package com.ruoyi.aitcommon.service;

import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

import cn.hutool.core.util.StrUtil;
import com.ruoyi.aitcommon.config.ConfigManager;
import com.ruoyi.aitcommon.utils.*;
import com.ruoyi.common.config.RuoYiConfig;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import com.alibaba.fastjson.JSONObject;
import com.mysql.cj.x.protobuf.MysqlxDatatypes.Array;
import com.ruoyi.aitcommon.anotation.ServiceLog;
import com.ruoyi.aitcommon.api.DBOperation;
import com.ruoyi.aitcommon.vo.DBConVO;

import cn.hutool.core.date.DateTime;

/**
 * SQL服务类
 * @author Administrator
 *
 */
@Service
public class SQLService {
//	@Autowired
//	DBOperation dboper;
	@Autowired
	IAitEntityInfoService infoService;
	@Autowired
	EntityCache entityCache;
	@Autowired
	ConfigManager configManager;
	Map<String,Object> lockMap=new HashMap<String,Object>();

	/**
	 * 获取数据库数据
	 * @param dbstr db链接参数,举例 config.aitconfig.datasource.u9db
	 * @param table 表名或视图名
	 * @param path path条件
	 * @return 数据集合
	 * @throws Exception
	 */
	public List<Map<String, Object>> getDBData(String dbstr,String table, String path) throws Exception {
		List<Map<String, Object>> retlist;
		JSONObject dbjson = null;
//        Path处理
		if(path==null) path="";
		if(ObjUtils.isNotBlank(path)){
			SQLUtils.validQueryPath(path);
			if(!path.startsWith("and")){
				path = " and " + path;
			}
		}
//			DB处理
		if (ObjUtils.isNotBlank(dbstr)) {
			dbjson = (JSONObject) configManager.getContextByParm(dbstr);
		}
//			构造SQL
		String sql = "select * from %s where 1=1 %s";
		sql = String.format(sql, table, path);

//			如果dbjson不空,查询外部数据库,否则查询ait本地库
		if (dbjson != null)
			retlist = SQLUtils.execQuery(sql, dbjson);
		else
			retlist = SQLUtils.execQuery(sql);
		return retlist;
	}

//	依据表名获取同步锁对象
	private Object getLockObject(String table) {
		if(!lockMap.containsKey(table)) {
			lockMap.put(table, table);
		}
		return lockMap.get(table);
	}
	
	/**
	 * 调用存储过程查询服务
	 * @param sql 查询sql,方式:{ call proc_aitquery } ,Oracle采用 { call proc_aitquery(?) } 
	 * @param db DB链接
	 * @return 查询后的结果集
	 * @throws Exception
	 */
	@ServiceLog("存储过程查询服务")
	public List<Map<String, Object>> execProcQuery(String sql, JSONObject db) throws Exception {
		if (db == null)
			throw new Exception("数据库配置为空");
		return SQLUtils.execProcQuery(sql, db);
	}

	/**
	 * 调用存储过程更新服务
	 * @param sql 查询sql,方式:{ call proc_aitupdate } 
	 * @param db DB链接
	 * @return 更新记录数
	 * @throws Exception
	 */
	@ServiceLog("存储过程更新服务")
	public int execProcUpdate(String sql, JSONObject db) throws Exception {
		if (db == null)
			throw new Exception("数据库配置为空");
		return SQLUtils.execProcUpdate(sql, db);
	}

	/**
	 * SQL查询服务
	 * @param sql 查询sql
	 * @param db DB链接
	 * @return 查询结果集
	 * @throws Exception
	 */
	@ServiceLog("SQL查询服务")
	public List<Map<String, Object>> execQuery(String sql, JSONObject db) throws Exception {
		if (db == null)
			throw new Exception("数据库配置为空");
		DBConVO db1 = JSONObject.toJavaObject(db, DBConVO.class);
		final List<Map<String, Object>> retlist = SQLUtils.execQuery(sql, db1);
		return retlist;
	}
	
	/**
	 * 将sql查询数据保存至excel文件
	 * @param sql
	 * @param db
	 * @param filename 文件名,不带路径
	 * @return 带路径的文件名,文件名会有变化
	 * @throws Exception
	 */
	public String execQuery2Excel(String sql, JSONObject db,String filename) throws Exception {
		List<Map<String, Object>> retlist = this.execQuery(sql, db);
//		该方法返回的是新文件名
        String filename1 = ExcelUtils.exportExcel(retlist, filename);
//		获取路径名
		String filePath = RuoYiConfig.getDownloadPath() + filename1;
        return filePath;
	}

	/**
	 * SQL分页查询服务
	 * @param sql 查询sql
	 * @param db DB链接
	 * @param pagenum 查询页数
	 * @param pagesize 页大小
	 * @return 当前页数据集合
	 * @throws Exception
	 */
	@ServiceLog("SQL查询服务")
	public List<Map<String, Object>> execQueryByPage(String sql, JSONObject db, String pagenum, String pagesize)
			throws Exception {
		if (db == null)
			throw new Exception("数据库配置为空");
		int num = Integer.parseInt(pagenum.toString());
		int size = Integer.parseInt(pagesize.toString());

		return SQLUtils.execQueryPage(sql, "","", db, num, size);
	}

	/**
	 * SQL查询,指定查询条件
	 * @param sql 查询SQL
	 * @param db DB链接
	 * @param path 查询条件
	 * @return 满足条件的数据集合
	 * @throws Exception
	 */
	@ServiceLog("SQL查询服务")
	public List<Map<String, Object>> execQueryByPath(String sql, JSONObject db, String path) throws Exception {
		if (db == null)
			throw new Exception("数据库配置为空");

		return SQLUtils.execQueryPath(sql, path, db);
	}

	/**
	 * SQL查询,指定查询参数
	 * @param sql 含参数的查询SQL,参数格式 %s,同String.format
	 * @param params 参数集合
	 * @param db DB链接
	 * @return 满足条件的数据集合
	 */
	@ServiceLog("SQL查询服务")
	public List<Map<String, Object>> execQueryParms(String sql, List<Object> params, JSONObject db) throws Exception {
		return SQLUtils.execQueryParams(sql, params,null, db);
	}

	/**
	 * SQL更新
	 * @param sql 更新SQL
	 * @param db DB链接
	 * @return 更新记录数
	 * @throws Exception
	 */
	@ServiceLog("SQL更新服务")
	public int execUpdate(String sql, JSONObject db) throws Exception {
		if (db == null)
			throw new Exception("数据库配置为空");
		DBConVO db1 = JSONObject.toJavaObject(db, DBConVO.class);
		return SQLUtils.execUpdate(sql, db1);
	}

	/**
	 * SQL更新,指定更新参数
	 * @param sql 包含参数的更新SQL,参数格式 %s,同String.format
	 * @param parms 参数集合
	 * @param db DB链接
	 * @return 更新记录数
	 * @throws Exception
	 */
	@ServiceLog("SQL更新服务")
	public int execUpdateParms(String sql, List<Object> parms, JSONObject db) throws Exception {
		if (db == null)
			throw new Exception("数据库配置为空");
		DBConVO db1 = JSONObject.toJavaObject(db, DBConVO.class);
		return SQLUtils.execUpdate(sql, parms, db1);
	}

	/**
	 * SQL更新,更新AIT本地库
	 * @param sql 更新SQL,参数格式 %s,同String.format
	 * @param parms 参数集合
	 * @return 更新记录数
	 * @throws Exception
	 */
	@ServiceLog("SQL更新服务")
	public int execLocalUpdateParms(String sql, List<Object> parms) throws Exception {
		return SQLUtils.execUpdate(sql, parms);
	}

	/**
	 * SQL批量更新
	 * @param sqls 更新SQL集合
	 * @param db DB链接
	 * @return 更新记录数
	 * @throws Exception
	 */
	@ServiceLog("SQL批量更新服务")
	public int execBatchUpdate(List<String> sqls, JSONObject db) throws Exception {
		if (db == null)
			throw new Exception("数据库配置为空");
		DBConVO db1 = JSONObject.toJavaObject(db, DBConVO.class);
		return SQLUtils.execUpdate(sqls, db1);
	}

	/**
	 * 将datas直接插入数据库
	 * 
	 * @param datas 要插入的数据
	 * @param db DB链接
	 * @param tablename 插入表名
	 * @param includes 包含字段,可以为""
	 * @param excludes 排除字段,可以为""
	 * @return 插入条数
	 * @throws Exception
	 */
	@ServiceLog("MAP插入服务")
	public int mapInsert(List<Map<String, Object>> datas, JSONObject db, String tablename, String includes,
			String excludes) throws Exception {
		if (db == null)
			throw new Exception("数据库配置为空");
		DBConVO db1 = JSONObject.toJavaObject(db, DBConVO.class);
		List<String> sqls = SQLUtils.ListMap2Insert(datas, tablename, includes, excludes, db1);
		int ret=0;
		
//		增加同步锁
		synchronized(this.getLockObject(tablename)) {
			ret = SQLUtils.execUpdate(sqls, db1);
		}
		
		return ret;
	}

	/**
	 * 将datas直接插入数据库 新增功能:缓存过滤,记录实体日志
	 * 
	 * @param datas 需要插入的数据
	 * @param db DB链接
	 * @param tablename 插入表名
	 * @param includes 包含字段,可以为""
	 * @param excludes 排除字段,可以为""
	 * @param entityInfo 实体日志映射
	 * @return 插入记录数
	 * @throws Exception
	 */
	@ServiceLog("MAP插入服务")
	public int mapInsertEx(List<Map<String, Object>> datas, JSONObject db, String tablename, String includes,
			String excludes, JSONObject entityInfo) throws Exception {
		int num = 0;
		ExceptionUtils.checkBlankOrUndefined(db, "db参数为空");
		ExceptionUtils.checkBlankOrUndefined(entityInfo, "entityInfo参数为空");

		DBConVO db1 = JSONObject.toJavaObject(db, DBConVO.class);
//		从缓存过滤重复数据
		List<Map<String, Object>> filterlist = entityCache.filterEntityChange(entityInfo, datas);
		
//		增加同步锁
		synchronized(this.getLockObject(tablename)) {
			for (Map<String, Object> data : filterlist) {
				try {
					List<String> sqls = SQLUtils.ListMap2Insert(Arrays.asList(data), tablename, includes, excludes, db1);
					int ret = SQLUtils.execUpdate(sqls.get(0), db1);
					if (entityInfo != null) {
						if (!entityInfo.containsKey("oper"))
							entityInfo.put("oper", "新增");
						infoService.insertAitEntityInfo(entityInfo, data, null);
					}
					num += ret;
					if (ret == 0)
						throw new Exception("插入记录数为0,数据:" + data.toString());
//					增加进度提示
					if (num % 1000 == 0)
						LogUtils.info("mapInsertEx 已插入记录数[" + num + "]条");
				} catch (Exception ex) {
					if (!entityInfo.containsKey("oper"))
						entityInfo.put("oper", "新增");
					infoService.insertAitEntityInfo(entityInfo, data, ex);
				}
			}
		}
		
		LogUtils.info("mapInsertEx 已完成,插入记录数[" + num + "]条");
		return num;
	}

	/**
	 * 将datas更新数据库,只update,不做insert和delete
	 * @param datas 需要更新的数据
	 * @param db DB链接
	 * @param tablename 更新表名
	 * @param key 匹配主键(tablename中的code字段),更新语句按照该主键匹配数据
	 * @param includes 更新包含字段,可以为""
	 * @param excludes 更新排除字段,可以为""
	 * @return 更新条数
	 * @throws Exception
	 */
	@ServiceLog("MAP更新服务")
	public int mapUpdate(List<Map<String, Object>> datas, JSONObject db, String tablename, String key, String includes,
			String excludes) throws Exception {
		return this.mapUpdateV2(datas,db,tablename,Arrays.asList(key),includes,excludes);
	}

	/**
	 * 将datas更新数据库,只update,不做insert和delete
	 * @param datas 需要更新的数据
	 * @param db DB链接
	 * @param tablename 更新表名
	 * @param keys 匹配主键(tablename中的code字段),更新语句按照该主键匹配数据,支持多个字段
	 * @param includes 更新包含字段,可以为""
	 * @param excludes 更新排除字段,可以为""
	 * @return 更新条数
	 */
	@ServiceLog("MAP更新服务")
	public int mapUpdateV2(List<Map<String, Object>> datas, JSONObject db, String tablename, List<String> keys, String includes,
						 String excludes) throws Exception {
		if (db == null)
			throw new Exception("数据库配置为空");
		DBConVO db1 = JSONObject.toJavaObject(db, DBConVO.class);
		List<String> sqls = SQLUtils.ListMap2Update(datas, tablename, keys, includes, excludes, db1);
//		增加同步锁
		int ret=0;
		synchronized(this.getLockObject(tablename)) {
			ret=SQLUtils.execUpdate(sqls, db1);
		}
//		批量更新数据并返回更新条数
		return ret;
	}
	/**
	 * 将datas更新数据库,只update,不做insert和delete 新增功能:缓存过滤,记录实体日志
	 * 针对每条数据单独处理,会比较慢
	 * @param datas datas 需要更新的数据
	 * @param db DB链接
	 * @param tablename 更新表名
	 * @param key 匹配主键(tablename中的code字段),更新语句按照该主键匹配数据
	 * @param includes 更新包含字段,可以为""
	 * @param excludes 更新排除字段,可以为""
	 * @param entityInfo 实体日志映射
	 * @return 更新条数
	 */
	@ServiceLog("MAP更新服务")
	public int mapUpdateEx(List<Map<String, Object>> datas, JSONObject db, String tablename, String key,
			String includes, String excludes, JSONObject entityInfo) throws Exception {
		int num = 0;
		ExceptionUtils.checkBlankOrUndefined(db, "db参数为空");
		ExceptionUtils.checkBlankOrUndefined(entityInfo, "entityInfo参数为空");

		DBConVO db1 = JSONObject.toJavaObject(db, DBConVO.class);
//		从缓存过滤重复数据
		List<Map<String, Object>> filterlist= entityCache.filterEntityChange(entityInfo, datas);
		
//		增加同步锁
		synchronized(this.getLockObject(tablename)) {
			for (Map<String, Object> data : filterlist) {
//				针对每条处理,因为要插入日志
				try {
//					获取更新语句
					List<String> sqls = SQLUtils.ListMap2Update(Arrays.asList(data), tablename, Arrays.asList(key), includes, excludes,
							db1);
//					执行SQL
					int ret = SQLUtils.execUpdate(sqls.get(0), db1);
					
					if (!entityInfo.containsKey("oper"))
						entityInfo.put("oper", "修改");
					infoService.insertAitEntityInfo(entityInfo, data, null);
					num += ret;
					if (ret == 0)
						throw new Exception("更新记录数为0,数据:" + data.toString());
//					增加进度提示
					if (num % 1000 == 0)
						LogUtils.info("mapUpdateEx 已更新记录数[" + num + "]条");
				} catch (Exception ex) {
					if (!entityInfo.containsKey("oper"))
						entityInfo.put("oper", "修改");
					infoService.insertAitEntityInfo(entityInfo, data, ex);
//					抛出异常后,后面的数据会停掉
//					throw ex;
				}
			}
		}
		
		LogUtils.info("mapUpdateEx 已完成,更新记录数[" + num + "]条");
		return num;
	}

	/**
	 * 数据保存或更新操作
	 * @param datas 数据集合
	 * @param db 数据连接
	 * @param tablename 表名
	 * @param keys 主键集合
	 * @param includes 包含字段
	 * @param excludes 排除字段
	 * @param entityInfo 
	 * @param extFields 扩展字段,提供datas之外的扩展字段设置默认值
	 * 比如:"exFields":{"ts":"${$dateService.dateTime(0)}"} 增加ts字段的默认值赋值
	 * @return 插入或更新条数
	 */
	@ServiceLog
	public int mapSaveV2(List<Map<String, Object>> datas, JSONObject db, String tablename, List<String> keys, String includes,
			String excludes, JSONObject entityInfo,Map<String,Object> extFields) throws Exception {
		int num = 0;
		ExceptionUtils.checkBlankOrUndefined(db, "db参数为空");
		ExceptionUtils.checkBlankOrUndefined(entityInfo, "entityInfo参数为空");
		
		DBConVO db1 = JSONObject.toJavaObject(db, DBConVO.class);
		boolean isinsert = false;
//		从缓存过滤重复数据
		List<Map<String, Object>> filterlist = entityCache.filterEntityChange(entityInfo, datas);
		
//		增加同步锁
		synchronized(this.getLockObject(tablename)) {
			for (Map<String, Object> data : filterlist) {
				try {
//					扩展处理
					Map<String, Object> dataex=null;
					if(extFields!=null) {
//						扩展字段默认值
						dataex=new LinkedHashMap<String,Object>();
						dataex.putAll(data);
						dataex.putAll(extFields);
					}else{
//						无特殊处理
						dataex=data;
					}
					
//					查询是否存在
					String sql = SQLUtils.ListMap2Has(data, tablename, keys);
					List<Map<String, Object>> retlist = SQLUtils.execQuery(sql, db1);
					List<String> sqls = null;
					if (retlist.size() == 0) {
						// 新增逻辑
						isinsert = true;
						entityInfo.put("oper", "新增");
						sqls = SQLUtils.ListMap2Insert(Arrays.asList(dataex), tablename, includes, excludes, db1);
					} else {
						// 修改逻辑
						isinsert = false;
						entityInfo.put("oper", "修改");
						sqls = SQLUtils.ListMap2Update(Arrays.asList(dataex), tablename, keys, includes, excludes, db1);
					}
//					更新数据库
					int ret = SQLUtils.execUpdate(sqls.get(0), db1);
//					记录实体日志
					if (isinsert)
						entityInfo.put("oper", "新增");
					else
						entityInfo.put("oper", "修改");
					infoService.insertAitEntityInfo(entityInfo, data, null);
					num += ret;
					if (ret == 0)
						throw new Exception("插入或修改记录数为0,数据:" + data.toString());
//					增加进度提示
					if (num % 1000 == 0)
						LogUtils.info("mapSave 已插入/更新记录数[" + num + "]条");

				} catch (Exception ex) {
					if (isinsert)
						entityInfo.put("oper", "新增");
					else
						entityInfo.put("oper", "修改");
					infoService.insertAitEntityInfo(entityInfo, data, ex);
				}
			}
		}
		
		LogUtils.info("mapSaveEx 已完成,插入/更新记录数[" + num + "]条");
		return num;
	}
	/**
	 * 新增ext参数
	 * @param datas
	 * @param db
	 * @param tablename
	 * @param key
	 * @param includes
	 * @param excludes
	 * @param entityInfo
	 * @param ext 扩展处理:
	 * <pre>
	 * 0 无扩展
	 * 1 增加ts字段处理
	 * </pre>
	 * @return
	 */
	@ServiceLog
	public int mapSaveTs(List<Map<String, Object>> datas, JSONObject db, String tablename, String key, String includes,
			String excludes, JSONObject entityInfo,Integer ext) throws Exception {
		Map<String,Object> extFields=new HashMap<String,Object>();
		if(ext==1) {
			extFields.put("ts", new DateTime());
		}
			
		return this.mapSaveV2(datas, db, tablename, Arrays.asList(key), includes, excludes, entityInfo, extFields);
	}
	/**
	 * 将datas直接插入或修改数据库表,不做delete
	 * 新增:缓存过滤,记录实体日志
	 * 新增:对象同步锁
	 * @param datas 需要保存的数据
	 * @param db DB链接
	 * @param tablename 保存表名
	 * @param key 匹配主键(tablename中的code字段),更新语句按照该主键匹配数据
	 * @param includes 包含字段,可以为""
	 * @param excludes 排除字段,可以为""
	 * @param entityInfo 实体日志映射
	 * @return 插入或更新条数
	 * @throws Exception
	 */
//	@ServiceLog("MAP保存服务")
	public int mapSaveEx(List<Map<String, Object>> datas, JSONObject db, String tablename, String key, String includes,
			String excludes, JSONObject entityInfo) throws Exception {
		return this.mapSaveTs(datas, db, tablename, key, includes, excludes, entityInfo,0);
	}

	/**
	 * 数据删除
	 * @param datas 需要删除的数据
	 * @param db DB链接
	 * @param tablename 表名
	 * @param key 匹配主键(tablename中的code字段),删除语句按照该主键匹配数据
	 * @return 删除条数
	 * @throws Exception
	 */
	@ServiceLog("MAP删除服务")
	public int mapDelete(List<Map<String, Object>> datas, JSONObject db, String tablename, String key)
			throws Exception {
		if (db == null)
			throw new Exception("数据库配置为空");
		DBConVO db1 = JSONObject.toJavaObject(db, DBConVO.class);
		List<String> sqls = SQLUtils.ListMap2Delete(datas, tablename, key);
		
//		增加同步锁
		int ret=0;
		synchronized(this.getLockObject(tablename)) {
			ret = SQLUtils.execUpdate(sqls, db1);
		}
		return ret;
	}

	
}
